﻿using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace S7Cat.Models
{
    public sealed class MultipleReader : IDisposable
    {
        internal Range[] Ranges { get; init; }
        internal byte[] BitAdrs { get; init; }

        private readonly bool isSharedBytes = false;
        internal byte[] DataBytes { get; init; }

        public int DataCount { get; init; }
        public MultipleReader(Span<DataItemAddress> addresses, byte[] dataBytes)
        {
            DataCount = addresses.Length;

            DataBytes = dataBytes;
            Ranges = ArrayPool<Range>.Shared.Rent(DataCount);
            BitAdrs = ArrayPool<byte>.Shared.Rent(DataCount);

            var offset = 0;

            for (int i = 0; i < addresses.Length; i++)
            {
                BitAdrs[i] = addresses[i].BitAdr;
                Ranges[i] = new Range(offset, offset + addresses[i].ByteCount);
                offset += addresses[i].ByteCount;
            }
        }

        internal MultipleReader(Span<DataItemAddress> addresses, byte[] dataBytes, bool isSharedBytes) : this(addresses, dataBytes)
        {
            this.isSharedBytes = isSharedBytes;
        }

        public T Read<T>(int index) where T : unmanaged, INumber<T>
        {
            if (index >= DataCount || index < 0)
            {
                throw new IndexOutOfRangeException();
            }

            var span = DataBytes.AsSpan(Ranges[index]);

            span.Reverse();
            return Unsafe.ReadUnaligned<T>(ref span[0]);
        }

        public bool Read(int index)
        {
            var b = Read<byte>(index);
            return BasePlc.ReadBool(b, 7 - BitAdrs[index]);
        }


        public T ReadStruct<T>(int index) where T : struct, IPlcStruct<T>
        {
            if (index >= DataCount || index < 0)
            {
                throw new IndexOutOfRangeException();
            }

            T.Deserialize(DataBytes[Ranges[index]], out T value);
            return value;
        }

        public Span<T> ReadSpan<T>(int index) where T : unmanaged, INumber<T>
        {
            if (index >= DataCount || index < 0)
            {
                throw new IndexOutOfRangeException();
            }

            var span = DataBytes.AsSpan(Ranges[index]);

            var size = Unsafe.SizeOf<T>();
            if (size > 1)
            {
                for (int i = 0; i < span.Length; i += size)
                {
                    span.Slice(i, size).Reverse();
                }
            }

            return MemoryMarshal.Cast<byte, T>(span);
        }

        private bool isDisposed = false;
        public void Dispose()
        {
            if (!isDisposed)
            {
                isDisposed = true;
                ArrayPool<Range>.Shared.Return(Ranges);
                ArrayPool<byte>.Shared.Return(BitAdrs);

                if (isSharedBytes)
                {
                    ArrayPool<byte>.Shared.Return(DataBytes);
                }

                GC.SuppressFinalize(this);
            }
        }

        ~MultipleReader()
        {
            if (!isDisposed)
            {
                Dispose();
            }
        }
    }
}
